// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

library;

import '../common/metrics.dart' show Metric, Metrics, CountMetric;
import '../common/tasks.dart' show CompilerTask;
import '../compiler.dart' show Compiler;
import '../constants/values.dart';
import '../deferred_load/output_unit.dart' show OutputUnit;
import '../elements/entities.dart';
import '../js/js.dart' as js_ast;
import '../js_backend/codegen_inputs.dart' show CodegenInputs;
import '../js_backend/inferred_data.dart';
import '../js_backend/namer.dart' show Namer;
import '../js_backend/runtime_types.dart' show RuntimeTypesChecks;
import '../js_model/js_strategy.dart';
import '../js_model/js_world.dart' show JClosedWorld;
import '../options.dart';
import '../universe/codegen_world_builder.dart';
import 'program_builder/program_builder.dart';
import 'startup_emitter/emitter.dart' as startup_js_emitter;
import 'startup_emitter/fragment_merger.dart';

import 'js_emitter.dart';
import 'model.dart';

/// Generates the code for all used classes in the program. Static fields (even
/// in classes) are ignored, since they can be treated as non-class elements.
///
/// The code for the containing (used) methods must exist in the `universe`.
class CodeEmitterTask extends CompilerTask {
  late final RuntimeTypesChecks _rtiChecks;
  late final NativeEmitter nativeEmitter;
  late final MetadataCollector metadataCollector;
  late final Emitter emitter;
  final Compiler _compiler;
  final bool _generateSourceMap;

  JsBackendStrategy get _backendStrategy => _compiler.backendStrategy;

  CompilerOptions get options => _compiler.options;

  /// The field is set after the program has been emitted.
  /// Contains a list of all classes that are emitted.
  /// Currently used for testing and dump-info.
  late final Set<ClassEntity> neededClasses;

  /// See [neededClasses] but for class types.
  late final Set<ClassEntity> neededClassTypes;

  EmitterMetrics? _emitterMetrics;
  EmitterMetrics get emitterMetrics => _emitterMetrics ??= EmitterMetrics();
  @override
  Metrics get metrics => _emitterMetrics ?? Metrics.none();

  CodeEmitterTask(this._compiler, this._generateSourceMap)
    : super(_compiler.measurer);

  @override
  String get name => 'Code emitter';

  void _finalizeRti(CodegenInputs codegen, CodegenWorld codegenWorld) {
    // Compute the required type checks to know which classes need a
    // 'is$' method.
    _rtiChecks = _backendStrategy.rtiChecksBuilder.computeRequiredChecks(
      codegenWorld,
      options,
    );
  }

  /// Creates the [Emitter] for this task.
  void createEmitter(
    Namer namer,
    CodegenInputs codegen,
    JClosedWorld closedWorld,
  ) {
    measure(() {
      nativeEmitter = NativeEmitter(
        closedWorld,
        _backendStrategy.nativeCodegenEnqueuer,
      );
      emitter = startup_js_emitter.EmitterImpl(
        _compiler.options,
        _compiler.reporter,
        _compiler.outputProvider,
        _compiler.dumpInfoRegistry,
        namer,
        closedWorld,
        codegen.rtiRecipeEncoder,
        nativeEmitter,
        _backendStrategy.sourceInformationStrategy,
        this,
        _generateSourceMap,
      );
      metadataCollector = MetadataCollector(
        _compiler.reporter,
        emitter,
        codegen.rtiRecipeEncoder,
      );
    });
  }

  int assembleProgram(
    Namer namer,
    JClosedWorld closedWorld,
    InferredData inferredData,
    CodegenInputs codegenInputs,
    CodegenWorld codegenWorld,
  ) {
    return measure(() {
      measureSubtask('finalize rti', () {
        _finalizeRti(codegenInputs, codegenWorld);
      });
      ProgramBuilder programBuilder = ProgramBuilder(
        _compiler.options,
        closedWorld.elementEnvironment,
        closedWorld.commonElements,
        closedWorld.outputUnitData,
        codegenWorld,
        _backendStrategy.nativeCodegenEnqueuer,
        closedWorld.backendUsage,
        closedWorld.nativeData,
        closedWorld.rtiNeed,
        closedWorld.interceptorData,
        _rtiChecks,
        codegenInputs.rtiRecipeEncoder,
        codegenWorld.oneShotInterceptorData,
        _backendStrategy.customElementsCodegenAnalysis,
        _backendStrategy.recordsCodegen,
        _backendStrategy.generatedCode,
        namer,
        this,
        closedWorld,
        closedWorld.fieldAnalysis,
        closedWorld.recordData,
        inferredData,
        _backendStrategy.sourceInformationStrategy,
        closedWorld.sorter,
        _rtiChecks.requiredClasses,
        closedWorld.elementEnvironment.mainFunction!,
      );
      int size = emitter.emitProgram(programBuilder, codegenWorld);
      neededClasses = programBuilder.collector.neededClasses;
      neededClassTypes = programBuilder.collector.neededClassTypes;
      return size;
    });
  }
}

/// Interface for the subset of the [Emitter] that can be used during modular
/// code generation.
///
/// Note that the emission phase is not itself modular but performed on
/// the closed world computed by the codegen enqueuer.
abstract class ModularEmitter {
  /// Returns the JS prototype of the given class [e].
  js_ast.Expression prototypeAccess(ClassEntity e);

  /// Returns the JS function representing the given function.
  ///
  /// The function must be invoked and can not be used as closure.
  js_ast.Expression staticFunctionAccess(FunctionEntity element);

  js_ast.Expression staticFieldAccess(FieldEntity element);

  /// Returns the JS function that must be invoked to get the value of the
  /// lazily initialized static.
  js_ast.Expression isolateLazyInitializerAccess(covariant FieldEntity element);

  /// Returns the closure expression of a static function.
  js_ast.Expression staticClosureAccess(covariant FunctionEntity element);

  /// Returns the JS constructor of the given element.
  ///
  /// The returned expression must only be used in a JS `new` expression.
  js_ast.Expression constructorAccess(ClassEntity e);

  /// Returns the JS name representing the type [e].
  js_ast.Name typeAccess(ClassEntity e);

  /// Returns the JS name representing the type variable [e].
  js_ast.Name typeVariableAccess(TypeVariableEntity e);

  /// Returns the JS code for accessing the embedded [global].
  js_ast.Expression generateEmbeddedGlobalAccess(String global);

  /// Returns the JS code for accessing the given [constant].
  js_ast.Expression constantReference(ConstantValue constant);

  /// Returns the JS code for accessing the global property [global].
  String generateEmbeddedGlobalAccessString(String global);
}

/// Interface for the emitter that is used during the emission phase on the
/// closed world computed by the codegen enqueuer.
///
/// These methods are _not_ available during modular code generation.
abstract class Emitter implements ModularEmitter {
  Program? get programForTesting;

  List<PreFragment>? get preDeferredFragmentsForTesting;

  /// The set of omitted [OutputUnits].
  Set<OutputUnit> get omittedOutputUnits;

  /// A map of loadId to list of [FinalizedFragments].
  Map<String, List<FinalizedFragment>> get finalizedFragmentsToLoad;

  /// The [FragmentMerger] itself.
  FragmentMerger get fragmentMerger;

  /// Uses the [programBuilder] to generate a model of the program, emits
  /// the program, and returns the size of the generated output.
  int emitProgram(ProgramBuilder programBuilder, CodegenWorld codegenWorld);

  /// Returns the JS prototype of the given interceptor class [e].
  js_ast.Expression interceptorPrototypeAccess(ClassEntity e);

  /// Returns the JS constructor of the given interceptor class [e].
  js_ast.Expression interceptorClassAccess(ClassEntity e);

  /// Returns the JS expression representing a function that returns 'null'
  js_ast.Expression generateFunctionThatReturnsNull();

  int compareConstants(ConstantValue a, ConstantValue b);
  bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant);

  /// Returns the size of the code generated for a given output [unit].
  Map<OutputUnit, int> get generatedSizes;
}

class EmitterMetrics implements Metrics {
  @override
  String get namespace => 'emitter';

  CountMetric hunkListElements = CountMetric('hunkListElements');

  @override
  Iterable<Metric> get primary => [];

  @override
  Iterable<Metric> get secondary => [hunkListElements];
}
